package solver

import (
	//"fmt"

	"gosudoku/set"
	"log"
	"slices"
)

func (g *Grid) AvoidRectangle2Filled(lines []House,
	aRHandler func([]House, int, []int, int) bool) (used bool) {
	for i, line1 := range lines {
		pairs := combine(chooseFilled(line1), 2)
		for _, pair := range pairs {
			for j := range lines {
				if i == j {
					continue
				}

				if !sameBoxes([]int{i, j}, pair) {
					continue
				}
				//g.PrintGrid()
				//fmt.Println(i, pair, j)
				if aRHandler(lines, i, pair, j) {
					used = true
					return
				}
			}
		}
	}
	return
}

func (g *Grid) ARType1(lines []House, i int, pair []int, j int) (used bool) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[2].filled() && !ps[3].filled() {
		return
	}

	var cand int
	var cell *Cell
	if ps[2].value == ps[1].value && ps[3].cands.Has(toCand(ps[0].value)) {
		cand = toCand(ps[0].value)
		cell = ps[3]
	} else if ps[3].value == ps[0].value && ps[2].cands.Has(toCand(ps[1].value)) {
		cand = toCand(ps[1].value)
		cell = ps[2]
	} else {
		return
	}

	cell.removeCand(cand)
	used = true
	if lines[i].kind == 0 {
		log.Printf("r%v%vc%v%v %v Avoid Rectangle Type1",
			i, j, pair[0], pair[1], toValue(cand))
	} else {
		log.Printf("c%v%vr%v%v %v Avoid Rectangle Type1",
			i, j, pair[0], pair[1], toValue(cand))
	}
	return
}

func (g *Grid) AvoidRectangleType1() (used bool) {
	if g.AvoidRectangle2Filled(g.rows[:], g.ARType1) {
		used = true
		return
	}
	return
}

func chooseFilled(line House) (filleds []int) {
	for cellNum, cell := range line.cells {
		if cell.filled() {
			filleds = append(filleds, cellNum)
		}
	}
	return
}

func (g *Grid) AvoidRectangleType2A() (used bool) {
	if g.AvoidRectangle2Filled(g.rows[:], g.ARType2A) {
		used = true
		return
	}
	if g.AvoidRectangle2Filled(g.cols[:], g.ARType2A) {
		used = true
		return
	}
	return
}

func (g *Grid) ARType2A(lines []House, i int, pair []int, j int) (used bool) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) || !ps[2].cands.Has(toCand(ps[1].value)) {
		return
	}

	if ps[2].cands.Count() != 2 || ps[3].cands.Count() != 2 {
		return
	}

	z := ps[2].cands.Intersect(ps[3].cands)
	if z.Count() != 1 {
		return
	}
	cand := z.GetList()[0]

	if g.clearupSeenCand(cand, ps[2:]) {
		log.Printf("r%v%vc%v%v %v%v %v Avoid Rectangle Type2A",
			i, j, pair[0], pair[1], ps[0].value, ps[1].value, toValue(cand))
		return true
	}
	return
}

func (g *Grid) AvoidRectangleType3A() (used bool) {
	if g.AvoidRectangle2Filled(g.rows[:], g.ARType3A) {
		used = true
		return
	}
	if g.AvoidRectangle2Filled(g.cols[:], g.ARType3A) {
		used = true
		return
	}
	return
}

func (g *Grid) ARType3A(lines []House, i int, pair []int, j int) (used bool) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) || !ps[2].cands.Has(toCand(ps[1].value)) {
		return
	}

	if verifyAR3AHouse(lines[j], ps[:], pair) {
		used = true
	}
	if ps[2].boxNum == ps[3].boxNum {
		if verifyAR3AHouse(g.boxes[ps[2].boxNum], ps[:],
			[]int{ps[2].cellNum, ps[3].cellNum}) {
			used = true
		}
	}

	if used {
		log.Printf("r%v%vc%v%v %v%v Avoid Rectangle Type3A",
			i, j, pair[0], pair[1],
			ps[0].value, ps[1].value)
	}

	return
}

func verifyAR3AHouse(house House, ps []*Cell, pair []int) (used bool) {
	rectCands := set.NewSet([]int{toCand(ps[0].value), toCand(ps[1].value)})
	remainCands := ps[2].cands.Union(ps[3].cands).Diff(rectCands)

	unsolvedCellNums := []int{}
	for cellNum, cell := range house.cells {
		if slices.Contains(pair, cellNum) || cell.solved() {
			continue
		}
		unsolvedCellNums = append(unsolvedCellNums, cellNum)
	}

	for cellCount := 1; cellCount <= 3; cellCount++ {
		ok, removeCellNums, removeCands := verifyURType3NakedSubSet(cellCount, house, remainCands, unsolvedCellNums)
		if !ok {
			continue
		}
		for _, cellNum := range removeCellNums {
			if house.cells[cellNum].removeCands(removeCands) {
				used = true
				return
			}
		}
		return
	}
	return
}

func (g *Grid) AvoidRectangleType3B() (used bool) {
	if g.AvoidRectangle2Filled(g.rows[:], g.ARType3B) {
		used = true
		return
	}
	if g.AvoidRectangle2Filled(g.cols[:], g.ARType3B) {
		used = true
		return
	}
	return
}

func (g *Grid) ARType3B(lines []House, i int, pair []int, j int) (used bool) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) || !ps[2].cands.Has(toCand(ps[1].value)) {
		return
	}

	if verifyAR3BHouse(lines[j], ps[:], pair) {
		used = true
	}
	if ps[2].boxNum == ps[3].boxNum {
		if verifyAR3BHouse(g.boxes[ps[2].boxNum], ps[:],
			[]int{ps[2].cellNum, ps[3].cellNum}) {
			used = true
		}
	}

	if used {
		log.Printf("r%v%vc%v%v %v%v Avoid Rectangle Type3B",
			i, j, pair[0], pair[1],
			ps[0].value, ps[1].value)
	}

	return
}

func verifyAR3BHouse(house House, ps []*Cell, pair []int) (used bool) {
	rectCands := set.NewSet([]int{toCand(ps[0].value), toCand(ps[1].value)})
	remainCands := ps[2].cands.Union(ps[3].cands).Diff(rectCands)

	unsolvedCellNums := []int{}
	for cellNum, cell := range house.cells {
		if slices.Contains(pair, cellNum) || cell.solved() {
			continue
		}
		unsolvedCellNums = append(unsolvedCellNums, cellNum)
	}

	for cellCount := 1; cellCount <= 3; cellCount++ {
		ok, removeCellNums, removeCands := verifyURType3HiddenSubSet(cellCount, house, rectCands, remainCands, unsolvedCellNums)
		if !ok {
			continue
		}
		for _, cellNum := range removeCellNums {
			if house.cells[cellNum].removeCands(removeCands) {
				used = true
				return
			}
		}
		return
	}
	return
}

func (g *Grid) AvoidRectangle1Filled(lines []House,
	aRHandler func([]House, int, []int, int) bool) (used bool) {
	for i, line1 := range lines {
		pairs := chooseFilledAndUnsolved(line1)
		for _, pair := range pairs {
			for j := range lines {
				if i == j {
					continue
				}

				if !sameBoxes([]int{i, j}, pair) {
					continue
				}
				if aRHandler(lines, i, pair, j) {
					used = true
					return
				}
			}
		}
	}
	return
}

func chooseFilledAndUnsolved(line House) (pairs [][]int) {
	filled := []int{}
	unsolved := []int{}
	for i, cell := range line.cells {
		if cell.filled() {
			filled = append(filled, i)
			continue
		}
		if !cell.solved() {
			unsolved = append(unsolved, i)
		}
	}
	for _, i := range filled {
		for _, j := range unsolved {
			pairs = append(pairs, []int{i, j})
		}
	}
	return
}

func (g *Grid) aRType2B(lines []House, i int, pair []int, j int) (used bool) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if ps[1].cands.Count() != 2 || ps[1].cands != ps[2].cands {
		return
	}

	var cands set.Set
	var cells []*Cell

	if ps[3].value == ps[0].value {
		used = true
		cands = ps[1].cands
		cells = ps[2:]
	} else if ps[3].cands.Has(toCand(ps[0].value)) &&
		ps[3].cands.Count() == 2 && ps[3].cands.Intersect(ps[1].cands).Count() == 1 {
		used = true
		cands = ps[1].cands.Diff(ps[3].cands)
		cells = ps[1:]
	} else {
		return
	}
	if g.clearupSeenCands(cands.GetList(), cells) {
		log.Printf("r%v%vc%v%v %v Avoidable Rectangle Type2B",
			i, j, pair[0], pair[1], cands.ValueString())
		return true
	}

	return
}

func (g *Grid) AvoidRectangleType2B() (used bool) {
	if g.AvoidRectangle1Filled(g.rows[:], g.aRType2B) {
		used = true
		return
	}
	return
}

func (g *Grid) aRType4C(lines []House, i int, pair []int, j int) (used bool) {
	var ps [4]*Cell
	ps[0] = lines[i].cells[pair[0]]
	ps[1] = lines[i].cells[pair[1]]
	ps[2] = lines[j].cells[pair[0]]
	ps[3] = lines[j].cells[pair[1]]

	if !ps[3].cands.Has(toCand(ps[0].value)) {
		return
	}

	cands := ps[1].cands.Intersect(ps[2].cands).Intersect(ps[3].cands)

	removeCand := -1
	row := g.rows[ps[3].rowNum]
	col := g.cols[ps[3].colNum]
	colNums := pair
	rowNums := []int{i, j}
	if lines[i].kind == 1 {
		colNums = []int{i, j}
		rowNums = pair
	}
	//fmt.Println(i, j, pair)
	//fmt.Println(colNums, rowNums)
	for _, cand := range cands.GetList() {
		if isOnlyTwoCand(cand, row, colNums) &&
			isOnlyTwoCand(cand, col, rowNums) {
			removeCand = toCand(ps[0].value)
			break
		}

	}
	if removeCand == -1 {
		return
	}
	used = true
	ps[3].removeCand(removeCand)
	log.Printf("r%v%vc%v%v %v Avoidable Rectangle Type4C",
		rowNums[0], rowNums[1], colNums[0], colNums[1], toValue(removeCand))
	return
}

func (g *Grid) AvoidRectangleType4C() (used bool) {
	if g.AvoidRectangle1Filled(g.rows[:], g.aRType4C) {
		used = true
		return
	}
	return
}
